{T}

编程范式游记(7)- 基于原型的编程范式 [2026重制版]

原文发布时间:2018年 重制时间:2026年6月 核心主题:原型继承、委托模式与现代JavaScript/TypeScript实践

核心变更说明

自2018年以来,基于原型的编程发生了重大变化:

  1. ECMAScript 2022+:Class Fields、Private Methods、Static Initialization Blocks正式标准化
  2. TypeScript 5.xsatisfies操作符、using声明、装饰器增强
  3. JavaScript引擎优化:V8/SpiderMonkey对原型链访问性能大幅提升
  4. Proxy/Reflect API成熟:元编程能力增强
  5. Object.groupBy():ES2024新增实用方法

数据来源


原型编程定义与思维导图

什么是基于原型的编程?

基于原型的编程(Prototype-based Programming)是一种OOP的实现方式,它不使用类(class)来定义对象的结构和行为,而是通过克隆(cloning)现有的对象来创建新对象,并通过**原型链(prototype chain)**实现属性和方法的共享与继承。

根据原文核心观点:

原型编程不使用类化的方式,直接在对象上进行修改和扩展,通过委托(delegation)机制在运行时查找属性和方法。

原型编程 vs 基于类的编程

图表渲染中…

JavaScript原型系统全景图

图表渲染中…

原型链示意图

图表渲染中…

语言特性演进时间线

图表渲染中…

代码示例对比(2018 vs 2026)

示例一:Person → Student 继承

❌ 2018年版本(传统原型写法)

javascript
// 原文中的ES5风格
function Person(fullName, email) {
    this.fullName = fullName;
    this.email = email;
 
    this.speak = function(){
        console.log("I speak English!");
    };
    this.introduction = function(){
        console.log("Hi, I am " + this.fullName);
    };
}
 
function Student(fullName, email, school, courses) {
    Person.call(this, fullName, email);
    this.school = school;
    this.courses = courses;
 
    this.introduction= function(){
        console.log("Hi, I am " + this.fullName +
            ". I am a student of " + this.school +
            ", I study "+ this.courses +".");
    };
 
    this.takeExams = function(){
        console.log("This is my exams time!");
    };
}
 
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

问题分析

  • 手动管理原型链容易出错
  • call()绑定this不够优雅
  • 方法重复定义浪费内存
  • 缺乏类型安全

✅ 2026年版本(现代TypeScript + ES2022+)

TypeScript 5.x - 完整类型安全的类继承

typescript
// 使用ES2022+ Class Features + TypeScript 5.x 类型系统
 
/**
 * 基础人员接口
 */
interface IPerson {
    readonly id: string;
    fullName: string;
    email: string;
    createdAt: Date;
 
    speak(): void;
    introduce(): string;
}
 
/**
 * 抽象基类 - 使用abstract约束子类行为
 */
abstract class BasePerson implements IPerson {
    // Public fields (ES2022)
    readonly id: string;
    fullName: string;
    email: string;
    readonly createdAt: Date;
 
    // Private field (ES2022 #private)
    #internalNotes: string[] = [];
 
    constructor(id: string, fullName: string, email: string) {
        this.id = id;
        this.fullName = fullName;
        this.email = email;
        this.createdAt = new Date();
    }
 
    // 抽象方法 - 子类必须实现
    abstract speak(): void;
 
    // 具体方法 - 可被重写
    introduce(): string {
        return `你好,我是 ${this.fullName}!`;
    }
 
    // Protected method - 子类可访问
    protected addNote(note: string): void {
        this.#internalNotes.push(`[${new Date().toISOString()}] ${note}`);
    }
 
    // Getter for notes (只读暴露)
    get notes(): ReadonlyArray<string> {
        return this.#internalNotes;
    }
 
    // Static factory method
    static create(id: string, name: string, email: string): BasePerson {
        throw new Error('必须在子类中实现');
    }
}
 
/**
 * 学生类 - 继承BasePerson
 */
class Student extends BasePerson {
    // 新增属性
    school: string;
    major: string;
    #gpa: number;  // 私有字段
    #courses: Map<string, number>;  // 课程及成绩
 
    constructor(
        id: string,
        fullName: string,
        email: string,
        school: string,
        major: string
    ) {
        super(id, fullName, email);
        this.school = school;
        this.major = major;
        this.#gpa = 0.0;
        this.#courses = new Map();
    }
 
    // 实现抽象方法
    speak(): void {
        console.log(`${this.fullName} 说:我会说英语和中文!`);
    }
 
    // 重写父类方法(override关键字)
    override introduce(): string {
        return `${super.introduce()} 我就读于 ${this.school},主修 ${this.major} 专业。`;
    }
 
    // 新增方法
    enroll(courseName: string, credits: number): void {
        if (this.#courses.has(courseName)) {
            console.log(`⚠️ 已选修课程: ${courseName}`);
            return;
        }
        this.#courses.set(courseName, credits);
        this.addNote(`选修课程: ${courseName} (${credits}学分)`);
        console.log(`✅ 选课成功: ${courseName}`);
    }
 
    takeExam(courseName: string, score: number): void {
        if (!this.#courses.has(courseName)) {
            throw new Error(`未选修课程: ${courseName}`);
        }
 
        const maxScore = 100;
        if (score < 0 || score > maxScore) {
            throw new Error('成绩必须在0-100之间');
        }
 
        this.addNote(`考试完成: ${courseName} - ${score}分`);
        this.#recalculateGPA();
    }
 
    // 私有辅助方法
    #recalculateGPA(): void {
        if (this.#courses.size === 0) {
            this.#gpa = 0.0;
            return;
        }
 
        let totalPoints = 0;
        let totalCredits = 0;
 
        // 假设所有课程都是4.0制
        for (const [_, credits] of this.#courses) {
            totalCredits += credits;
            totalPoints += 4.0 * credits;  // 简化计算
        }
 
        this.#gpa = totalPoints / totalCredits;
    }
 
    // 公开getter
    get gpa(): number {
        return Math.round(this.#gpa * 100) / 100;
    }
 
    get courseCount(): number {
        return this.#courses.size;
    }
 
    // 静态工厂方法
    static create(
        id: string,
        name: string,
        email: string,
        school: string,
        major: string
    ): Student {
        return new Student(id, name, email, school, major);
    }
 
    // 类方法 - 从JSON重建实例
    static fromJSON(data: Record<string, unknown>): Student {
        const student = Student.create(
            data.id as string,
            data.fullName as string,
            data.email as string,
            data.school as string,
            data.major as string
        );
 
        // 恢复私有状态(通过内部API)
        if (data.courses) {
            for (const [course, credits] of Object.entries(data.courses as Record<string, number>)) {
                student.enroll(course, credits);
            }
        }
 
        return student;
    }
}
 
// ==================== 使用示例 ====================
 
// 创建学生实例
const student = Student.create(
    'stu-001',
    '张三',
    'zhangsan@example.com',
    '北京大学',
    '计算机科学与技术'
);
 
// 调用方法
student.speak();
console.log(student.introduce());
 
// 选课
student.enroll('数据结构与算法', 4);
student.enroll('操作系统', 3);
student.enroll('计算机网络', 3);
 
// 参加考试
student.takeExam('数据结构与算法', 92);
student.takeExam('操作系统', 88);
 
// 访问公开信息
console.log('\n📊 学生信息:');
console.log(`姓名: ${student.fullName}`);
console.log(`学校: ${student.school}`);
console.log(`专业: ${student.major}`);
console.log(`已选课程数: ${student.courseCount}`);
console.log(`GPA: ${student.gpa}`);
 
// 访问笔记(只读)
console.log('\n📝 操作日志:');
for (const note of student.notes) {
    console.log(`  ${note}`);
}
 
// 序列化为JSON
const jsonStr = JSON.stringify(student, null, 2);
console.log('\n💾 JSON序列化:');
console.log(jsonStr.substring(0, 200) + '...');
 
// 从JSON恢复
const restored = Student.fromJSON(JSON.parse(jsonStr));
console.log('\n♻️ 恢复后:');
console.log(restored.introduce());

纯JavaScript (ES2022+) - 无需编译的现代写法

javascript
// 使用最新的ECMAScript特性(无需TypeScript)
 
class Person {
    // 公共字段声明 (ES2022)
    id;
    fullName;
    email;
    #createdAt;  // 私有字段
 
    // 静态字段
    static instanceCount = 0;
 
    constructor(id, fullName, email) {
        this.id = id;
        this.fullName = fullName;
        this.email = email;
        this.#createdAt = new Date();
        Person.instanceCount++;
    }
 
    // 方法
    speak() {
        console.log(`${this.fullName}: Hello!`);
    }
 
    introduce() {
        return `我是 ${this.fullName}`;
    }
 
    // Getter
    get age() {
        return Date.now() - this.#createdAt.getTime();
    }
 
    // 静态方法
    static compare(a, b) {
        return a.fullName.localeCompare(b.fullName);
    }
 
    // 私有方法
    #validateEmail(email) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
}
 
class Student extends Person {
    #gpa;
    #grades = new Map();
 
    constructor(id, fullName, email, school, major) {
        super(id, fullName, email);
        this.school = school;
        this.major = major;
        this.#gpa = 0;
    }
 
    // 重写方法
    introduce() {
        return `${super.introduce()},我就读于${this.school}`;
    }
 
    // 新方法
    addGrade(course, grade) {
        this.#grades.set(course, grade);
        this.#calculateGPA();
    }
 
    #calculateGPA() {
        if (this.#grades.size === 0) {
            this.#gpa = 0;
            return;
        }
        let sum = 0;
        for (const grade of this.#grades.values()) {
            sum += grade;
        }
        this.#gpa = sum / this.#grades.size;
    }
 
    get gpa() {
        return Math.round(this.#gpa * 100) / 100;
    }
}
 
// 使用
const s = new Student('1', '李四', 'li@test.com', '清华', 'CS');
s.speak();
console.log(s.introduce());
s.addGrade('数学', 95);
s.addGrade('英语', 88);
console.log(`GPA: ${s.gpa}`);
 
// 原型链验证
console.log(s instanceof Student);     // true
console.log(s instanceof Person);       // true
console.log(Student.prototype instanceof Person.prototype.constructor); // true

示例二:动态原型修改与Mixin

❌ 2018年版本(直接修改原型)

javascript
// 原文中的直接修改方式
var foo = {name: "foo", one: 1, two: 2};
var bar = {three: 3};
bar.__proto__ = foo;  // 直接设置原型
bar.one;              // 1 (从原型获取)

问题分析

  • 直接操作__proto__影响性能
  • 不符合最佳实践
  • 在严格模式下可能受限

✅ 2026年版本(Proxy + Mixin组合)

TypeScript - Mixin模式(组合优于继承)

typescript
// ====== Mixin 工具函数 ======
 
// Type-safe mixin工厂函数
type Constructor<T = object> = new (...args: any[]) => T;
 
// Timestampable Mixin - 添加时间戳功能
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class Timestamped extends Base {
        #timestamp!: number;
 
        constructor(...args: any[]) {
            super(...args);
            this.#timestamp = Date.now();
        }
 
        get timestamp(): number {
            return this.#timestamp;
        }
 
        get age(): number {
            return Date.now() - this.#timestamp;
        }
 
        refresh(): void {
            this.#timestamp = Date.now();
        }
    };
}
 
// Serializable Mixin - 添加序列化功能
function Serializable<TBase extends Constructor>(Base: TBase) {
    return class Serializable extends Base {
        serialize(): string {
            return JSON.stringify(this);
        }
 
        static deserialize<T>(json: string): T {
            const obj = JSON.parse(json);
            return Object.assign(new (this as any)(), obj);
        }
    };
}
 
// Validatable Mixin - 添加验证功能
function Validatable<TBase extends Constructor>(Base: TBase) {
    return class Validatable extends Base {
        #errors: string[] = [];
        #rules: Map<keyof this, (value: any) => string | null> = new Map();
 
        addRule<K extends keyof this>(
            property: K,
            validator: (value: this[K]) => string | null
        ): void {
            this.#rules.set(property, validator as any);
        }
 
        validate(): boolean {
            this.#errors = [];
            let isValid = true;
 
            for (const [property, validator] of this.#rules) {
                const error = validator(this[property]);
                if (error) {
                    this.#errors.push(error);
                    isValid = false;
                }
            }
 
            return isValid;
        }
 
        get errors(): ReadonlyArray<string> {
            return [...this.#errors];
        }
    };
}
 
// ====== 使用Mixins构建实体类 ======
 
// 基础实体
class BaseEntity {
    id: string;
    version: number;
 
    constructor(id: string) {
        this.id = id;
        this.version = 1;
    }
 
    incrementVersion(): void {
        this.version++;
    }
}
 
// 组合多个Mixins
const EnhancedEntity = Timestamped(Serializable(Validatable(BaseEntity)));
 
// 用户实体
class User extends EnhancedEntity {
    name: string;
    email: string;
    role: 'admin' | 'user' | 'guest';
 
    constructor(id: string, name: string, email: string, role: User['role']) {
        super(id);
        this.name = name;
        this.email = email;
        this.role = role;
 
        // 添加验证规则
        this.addRule('email', (value: string) => {
            if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                return '邮箱格式无效';
            }
            return null;
        });
 
        this.addRule('name', (value: string) => {
            if (value.length < 2) {
                return '名称至少2个字符';
            }
            return null;
        });
    }
 
    // 自定义序列化逻辑
    override serialize(): string {
        const data = {
            id: this.id,
            name: this.name,
            email: this.email,
            role: this.role,
            version: this.version,
            createdAt: this.timestamp,
        };
        return JSON.stringify(data);
    }
}
 
// ==================== 使用示例 ====================
 
const user = new User('user-001', '王五', 'wangwu@test.com', 'admin');
 
console.log('=== 用户信息 ===');
console.log(`ID: ${user.id}`);
console.log(`名称: ${user.name}`);
console.log(`邮箱: ${user.email}`);
console.log(`角色: ${user.role}`);
console.log(`版本: ${user.version}`);
console.log(`创建时间: ${new Date(user.timestamp).toLocaleString()}`);
console.log(`存活时间: ${(user.age / 1000).toFixed(0)}秒`);
 
// 验证
console.log('\n=== 验证测试 ===');
console.log(`验证结果: ${user.validate() ? '✅ 通过' : '❌ 失败'}`);
if (user.errors.length > 0) {
    console.log('错误:', user.errors);
}
 
// 序列化
console.log('\n=== 序列化 ===');
const json = user.serialize();
console.log(json);
 
// 反序列化
console.log('\n=== 反序列化 ===');
const restored = User.deserialize<User>(json);
console.log(restored.name);  // "王五"
console.log(restored.email); // "wangwu@test.com"

Proxy API - 元编程能力

typescript
// 使用Proxy实现观察者模式
function makeObservable<T extends object>(target: T): T {
    const listeners = new Map<string, Set<Function>>();
 
    const handler: ProxyHandler<T> = {
        set(obj, prop, value) {
            const oldValue = (obj as any)[prop];
            const result = Reflect.set(obj, prop, value);
 
            // 触发变化通知
            if (result && oldValue !== value) {
                const propListeners = listeners.get(String(prop));
                if (propListeners) {
                    propListeners.forEach(listener =>
                        listener(prop, oldValue, value)
                    );
                }
 
                // 通用变化监听器
                const globalListeners = listeners.get('*');
                if (globalListeners) {
                    globalListeners.forEach(listener =>
                        listener(prop, oldValue, value)
                    );
                }
            }
 
            return result;
        },
    };
 
    const proxy = new Proxy(target, handler);
 
    // 添加订阅方法
    (proxy as any).subscribe = (
        property: string | '*',
        callback: (prop: string, oldVal: any, newVal: any) => void
    ) => {
        if (!listeners.has(property)) {
            listeners.set(property, new Set());
        }
        listeners.get(property)!.add(callback);
 
        // 返回取消订阅函数
        return () => {
            listeners.get(property)?.delete(callback);
        };
    };
 
    return proxy;
}
 
// 使用
const state = makeObservable({
    user: { name: '初始用户', count: 0 },
    theme: 'light',
});
 
// 订阅变化
const unsubUser = state.subscribe('user', (prop, oldVal, newVal) => {
    console.log(`📢 ${prop} 变化:`, oldVal, '→', newVal);
});
 
const unsubAny = state.subscribe('*', (prop, oldVal, newVal) => {
    console.log(`🔔 任何属性变化: ${prop}`);
});
 
// 修改状态
state.user = { name: '新用户', count: 10 };  // 触发两个监听器
state.theme = 'dark';  // 只触发通用监听器
 
// 取消订阅
unsubUser();
state.user = { name: '再次变化', count: 20 };  // 只触发通用监听器

适用场景分析

何时选择原型/委托模式?

图表渲染中…

原型模式的典型应用

场景推荐程度实现方式代表框架
UI组件继承⭐⭐⭐⭐⭐Class + MixinReact, Vue
插件架构⭐⭐⭐⭐⭐Prototype chainVSCode, Webpack
游戏实体⭐⭐⭐⭐ECS + ComponentsUnity ECS, Bevy
API客户端⭐⭐⭐⭐Class extendsAxios封装
配置对象⭐⭐⭐⭐Object.create各种SDK

最佳实践清单

✅ 原型编程最佳实践(2026年版)

1. 优先使用class语法糖

typescript
// ❌ 手动操作原型链(过时)
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() { ... };
 
function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() { ... };
 
// ✅ 使用class(更清晰)
class Animal {
    constructor(public name: string) {}
    speak(): void { ... }
}
 
class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name);
    }
    bark(): void { ... }
}

2. 善用组合(Mixin)替代深层继承

typescript
// ✅ Mixin模式提供灵活性
const CanFly = (Base: typeof GameObject) => class extends Base {
    fly(): void { console.log('飞行中...'); }
};
 
const CanSwim = (Base: typeof GameObject) => class extends Base {
    swim(): void { console.log('游泳中...'); }
};
 
// 按需组合
class Duck extends CanSwim(CanFly(GameObject)) {
    quack(): void { console.log('嘎嘎!'); }
}
 
class Penguin extends CanSwim(GameObject) {
    waddle(): void { console.log('摇摇摆摆...'); }
}

3. 使用Private字段保护封装

javascript
// ES2022+: 真正的私有字段(#)
class BankAccount {
    #balance = 0;
    #owner;
    #transactionHistory = [];
 
    constructor(owner, initialBalance) {
        this.#owner = owner;
        this.#balance = initialBalance;
    }
 
    deposit(amount) {
        if (amount <= 0) throw new Error('金额必须为正');
        this.#balance += amount;
        this.#logTransaction('deposit', amount);
    }
 
    withdraw(amount) {
        if (amount > this.#balance) throw new Error('余额不足');
        this.#balance -= amount;
        this.#logTransaction('withdraw', amount);
    }
 
    get balance() {
        return this.#balance;  // 只读访问
    }
 
    #logTransaction(type, amount) {
        this.#transactionHistory.push({ type, amount, date: new Date() });
    }
}

4. 避免直接修改内置原型

typescript
// ❌ 危险:污染全局原型
Array.prototype.first = function() {
    return this[0];
};
 
// ✅ 安全:使用工具函数或扩展
function first<T>(arr: T[]): T | undefined {
    return arr[0];
}
 
// 或者使用模块扩展
declare global {
    interface Array<T> {
        myCustomMethod(): T[];
    }
}
Array.prototype.myCustomMethod = function() { /* ... */ };

延伸资源与学习路径

📚 官方权威资源

  1. MDN - Inheritance and the prototype chain

  2. ECMAScript Specification - Objects

  3. You Don't Know JS: this & Object Prototypes

  4. JavaScript: The Core


总结

🎯 原型编程核心要点

  1. 原型链是JavaScript的灵魂

    • 理解__proto__prototypeconstructor三者的关系
    • 属性查找沿原型链向上进行
  2. Class是语法糖

    • 底层仍然是原型继承
    • 但提供了更清晰的语法和更好的工具支持
  3. 组合优于多层继承

    • Mixin模式提供灵活的代码复用
    • 避免菱形继承等问题
  4. ES2022+ Private Fields是真私有

    • 使用#前缀的字段外部无法访问
    • 比闭包模拟的"私有"更可靠

💡 2026年的趋势

  • Records & Types (Stage 2):真正的不可变值类型
  • Decorator Metadata Protocol:统一的元数据标准
  • Pattern Matching增强:switch表达式支持对象匹配
  • ShadowRealms (Stage 1):域内原型隔离

记住:原型编程的核心思想是"委托而非继承"。理解这一点,就能写出更灵活、更符合JavaScript哲学的代码。


相关文章导航

参考来源